﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace SFP.CollisionDetection
{
    public abstract class Collision
    {
        private const double TOUCH_DIST = 0.5;

        private static long setIDCounter = -1;
        internal static Dictionary<long, HashSet<CollisionInfo>> collisionInfoDict = new Dictionary<long, HashSet<CollisionInfo>>();
        internal static HashSet<CollisionInfo> obstacleCollisionInfos = new HashSet<CollisionInfo>();

        public static void clean()
        {
            collisionInfoDict.Clear();
            obstacleCollisionInfos.Clear();
            setIDCounter = -1;
        }

        public static long collisionsForGameObjects(HashSet<GameObject> gameObjects, Rectangle playerRect, bool areObstacles)
        {
            setIDCounter++;
            HashSet<CollisionInfo> collisionCollisionInfoSet = new HashSet<CollisionInfo>();

            foreach (GameObject gameObj in gameObjects)
            {
                double overlap;
                CollisionType collision = Collision.checkCollision(gameObj.GetRectangle(), playerRect, out overlap);
                if (collision == CollisionType.None) continue;

                CollisionInfo collisionCollisionInfo = new CollisionInfo(setIDCounter, areObstacles, gameObj, collision, overlap);
                collisionCollisionInfoSet.Add(collisionCollisionInfo);
                if (areObstacles) obstacleCollisionInfos.Add(collisionCollisionInfo);
            }
            collisionInfoDict.Add(setIDCounter, collisionCollisionInfoSet);

            return setIDCounter;
        }

        private static CollisionType checkCollision(Rectangle gameObj, Rectangle player, out double overlap)
        {
            double thisHalfWidth = gameObj.Width / 2d;
            double thatHalfWidth = player.Width / 2d;

            double thisCenterX = gameObj.X + thisHalfWidth;
            double thatCenterX = player.X + thatHalfWidth;
            double centerDistX = thatCenterX - thisCenterX;

            CollisionType collisionX;
            if (centerDistX < 0) collisionX = CollisionType.FromLeft;
            else collisionX = CollisionType.FromRight;

            double overlapX = (thisHalfWidth + thatHalfWidth) - absouluteValue(centerDistX);

            if (overlapX <= 0)
            {
                overlap = 0;
                return CollisionType.None;
            }

            double thisHalfHeight = gameObj.Height / 2d;
            double thatHalfHeight = player.Height / 2d;

            double thisCenterY = gameObj.Y - thisHalfHeight;
            double thatCenterY = player.Y - thatHalfHeight;
            double centerDistY = thisCenterY - thatCenterY;

            CollisionType collisionY;
            if (centerDistY < 0) collisionY = CollisionType.FromBeneathe;
            else collisionY = CollisionType.FromAbove;

            double overlapY = (thisHalfHeight + thatHalfHeight) - absouluteValue(centerDistY);

            if (overlapY <= -TOUCH_DIST || overlapX <= -TOUCH_DIST)
            {
                overlap = 0;
                return CollisionType.None;
            }

            if (overlapX > overlapY)
            {
                if (overlapY <= 0)
                    collisionY = collisionY == CollisionType.FromAbove ? CollisionType.TouchesTop : CollisionType.TouchesBottom;

                overlap = overlapY;
                return collisionY;
            }
            else
            {
                if (overlapX <= 0)
                    collisionX = collisionX == CollisionType.FromRight ? CollisionType.TouchesRight : CollisionType.TouchesLeft;

                overlap = overlapX;
                return collisionX;
            }
        }

        public static void processFilters(Direction vMoveDirection, Direction hMoveDirection)
        {
            processFilters(vMoveDirection, hMoveDirection, false);
        }

        public static void processFilters(Direction vMoveDirection, Direction hMoveDirection, bool filterNoneDirection)
        {
            movementFilter(vMoveDirection, hMoveDirection, filterNoneDirection);
            gameObjectPositionFilter();
        }

        private static void movementFilter(Direction vMoveDirection, Direction hMoveDirection, bool filterNoneDirection)
        {
            foreach (CollisionInfo collisionCollisionInfo in new List<CollisionInfo>(obstacleCollisionInfos))
            {
                if (collisionCollisionInfo.Collision == CollisionType.FromAbove || collisionCollisionInfo.Collision == CollisionType.TouchesTop)
                {
                    if (vMoveDirection != Direction.Down && (filterNoneDirection || vMoveDirection != Direction.None))
                        removeCollision(collisionCollisionInfo);
                }
                else if (collisionCollisionInfo.Collision == CollisionType.FromBeneathe || collisionCollisionInfo.Collision == CollisionType.TouchesBottom)
                {
                    if (vMoveDirection != Direction.Up && (filterNoneDirection || vMoveDirection != Direction.None))
                        removeCollision(collisionCollisionInfo);
                }
                else if (collisionCollisionInfo.Collision == CollisionType.FromLeft || collisionCollisionInfo.Collision == CollisionType.TouchesLeft)
                {
                    if (hMoveDirection != Direction.Right && (filterNoneDirection || hMoveDirection != Direction.None))
                        removeCollision(collisionCollisionInfo);
                }
                else if (collisionCollisionInfo.Collision == CollisionType.FromRight || collisionCollisionInfo.Collision == CollisionType.TouchesRight)
                {
                    if (hMoveDirection != Direction.Left && (filterNoneDirection || hMoveDirection != Direction.None))
                        removeCollision(collisionCollisionInfo);
                }
            }
        }

        private static void gameObjectPositionFilter()
        {
            HashSet<CollisionInfo> validCollisions = new HashSet<CollisionInfo>();

            foreach (CollisionInfo nextCollisionCollisionInfo in new List<CollisionInfo>(obstacleCollisionInfos))
            {
                GameObject nextGameObj = nextCollisionCollisionInfo.GameObject;
                CollisionType nextCollision = nextCollisionCollisionInfo.Collision;

                if (nextCollision != CollisionType.None)
                {
                    bool discardCollision = false;
                    if (nextCollision == CollisionType.FromAbove || nextCollision == CollisionType.TouchesTop || nextCollision == CollisionType.FromBeneathe)
                    {
                        foreach (CollisionInfo collisionCollisionInfo in validCollisions)
                        {
                            CollisionType collision = collisionCollisionInfo.Collision;
                            GameObject gameObj = collisionCollisionInfo.GameObject;
                            if (collision == CollisionType.FromLeft)
                            {
                                if (intValue(nextGameObj.X) >= intValue(gameObj.X))
                                    discardCollision = true;
                            }
                            else if (collision == CollisionType.FromRight)
                            {
                                if (intValue(nextGameObj.X) + nextGameObj.Width <= intValue(gameObj.X) + gameObj.Width)
                                    discardCollision = true;
                            }
                        }
                    }
                    else if (nextCollision == CollisionType.FromLeft || nextCollision == CollisionType.FromRight)
                    {
                        foreach (CollisionInfo collisionCollisionInfo in new List<CollisionInfo>(validCollisions))
                        {
                            CollisionType collision = collisionCollisionInfo.Collision;
                            GameObject gameObj = collisionCollisionInfo.GameObject;

                            if (collision == CollisionType.FromAbove || collision == CollisionType.TouchesTop || collision == CollisionType.FromBeneathe)
                            {
                                if (nextCollision == CollisionType.FromLeft)
                                {
                                    if (intValue(gameObj.X) >= intValue(nextGameObj.X))
                                        validCollisions.Remove(collisionCollisionInfo);
                                }
                                else if (nextCollision == CollisionType.FromRight)
                                {
                                    if (intValue(gameObj.X) + gameObj.Width <= intValue(nextGameObj.X) + nextGameObj.Width)
                                        validCollisions.Remove(collisionCollisionInfo);
                                }
                            }
                        }
                    }
                    if (!discardCollision) validCollisions.Add(nextCollisionCollisionInfo);
                }
            }

            foreach (CollisionInfo collisionCollisionInfo in new List<CollisionInfo>(obstacleCollisionInfos))
            {
                if (!validCollisions.Contains(collisionCollisionInfo))
                {
                    removeCollision(collisionCollisionInfo);
                }
            }
        }

        private static void removeCollision(CollisionInfo collisionCollisionInfo)
        {
            if (obstacleCollisionInfos.Contains(collisionCollisionInfo))
                obstacleCollisionInfos.Remove(collisionCollisionInfo);

            HashSet<CollisionInfo> collisionCollisionInfoSet;
            collisionInfoDict.TryGetValue(collisionCollisionInfo.SetID, out collisionCollisionInfoSet);
            collisionCollisionInfoSet.Remove(collisionCollisionInfo);
        }

        private static double absouluteValue(double value)
        {
            if (value < 0) value *= -1;
            return value;
        }

        private static int intValue(double value)
        {
            return (int)Math.Round(value, 0, MidpointRounding.AwayFromZero);
        }
    }
}
